// UniqueMachineID.cpp: implementation of the CUniqueMachineID class.
//
//////////////////////////////////////////////////////////////////////

//#include "stdafx.h"

// IMPORTANT -- Make sure that changes to the composition of Unique Machine ID are accompanied
//				by changes to the serialization / deserialization, allowing for old clients to
//				 check machine id.

#pragma warning(disable: 4101)

// When you compile a release version, global optimization is enabled by default.
// MSVC is fairly buggy with global optimization so we disable it.
#if defined(_MSC_VER)
#pragma optimize("g", off)
#endif

#include <windows.h>
#include <objbase.h>
#include <Iphlpapi.h>
#include <Rpcdce.h>
#include <nb30.h>
#include <malloc.h>
#include <tchar.h>

//#include <setupapi.h>
//#include <devguid.h>
//#include <regstr.h>

#include "lib.h"

#include "UniqueMachineID.h"


#define BUFFERSIZE 2048


//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

CUniqueMachineID::CUniqueMachineID() :	m_bHardDriveSerialNumberFound(false),
										m_bMacAddressFound(false),
										m_bCustomGUIDFound(false),
										m_bSystemTimerGUIDFound(false),
										m_bProductIDFound(false),
										m_pData(0)
{
	_Reset();
}

void
CUniqueMachineID::_Reset()
{
	_ResetHardDriveSerialNumber();
	_ResetMACAddress();
	_ResetCustomGUID();
	_ResetSystemTimerGUID();
	_ResetProductID();

	if (m_pData)
	{
		delete [] m_pData;
		m_pData = 0;
	}
}


void
CUniqueMachineID::_ResetCustomGUID()
{
	m_bCustomGUIDFound = false;
	memset(m_bytCustomGUID, '\0', GUID_BYTE_LENGTH);
}

void
CUniqueMachineID::_ResetHardDriveSerialNumber()
{
	m_bHardDriveSerialNumberFound = false;
	memset(m_bytHardDriveSerialNumber, '\0', HARD_DRIVE_SERIAL_NUMBER_LENGTH);
}

void
CUniqueMachineID::_ResetMACAddress()
{
	m_bMacAddressFound = false;
	memset(m_bytMACAddress, '\0', MAC_ADDRESS_BYTE_LENGTH);
}

void
CUniqueMachineID::_ResetSystemTimerGUID()
{
	m_bSystemTimerGUIDFound = false;
	memset(m_bytSystemTimerGUID, '\0', GUID_BYTE_LENGTH);
}

void
CUniqueMachineID::_ResetProductID()
{
	m_bProductIDFound = false;
	memset(m_bytProductID, '\0', PRODUCT_ID_LENGTH);
}

CUniqueMachineID::~CUniqueMachineID()
{
	if (m_pData)
	{
		delete [] m_pData;
	}
}

bool
CUniqueMachineID::UniqueIdFound() const
{
	return (m_bHardDriveSerialNumberFound ||
			m_bMacAddressFound ||
			m_bCustomGUIDFound ||
			m_bProductIDFound);
}

void
CUniqueMachineID::CreateUniqueMachineID()
{
	// if at least one of these values is successful, then
	//	we have a unique identifier.  Otherwise, the call failed
	_GetHardDriveSerialNumberFromClient();
	_GetMacAddressFromClient();
	_GetGUIDFromClient();
	_GetProductID();
//	_GetSystemTimerGUID();
}

void
CUniqueMachineID::_GetHardDriveSerialNumberFromClient()
{
	try
	{
		DWORD serial = 0;
		if (::GetVolumeInformation(	  TEXT("c:\\"),
									  0,
									  0,
									  (DWORD*)&serial,
									  0,
									  0,
									  0,
									  0))
		{
			memcpy(m_bytHardDriveSerialNumber, &serial, HARD_DRIVE_SERIAL_NUMBER_LENGTH);
 			m_bHardDriveSerialNumberFound = true;
		}

		if (!m_bHardDriveSerialNumberFound)
		{
			_ResetHardDriveSerialNumber();
		}
	}
	catch (...)
	{
		_ResetHardDriveSerialNumber();
		m_bHardDriveSerialNumberFound = false;
	}
}

typedef DWORD (WINAPI *GetIfTableFunc)(OUT PMIB_IFTABLE pIfTable, IN OUT PULONG pdwSize, IN BOOL bOrder);

void
CUniqueMachineID::_GetMacAddressFromClient()
{
	try
	{
		HMODULE hIpHelpAPI = 0;
		GetIfTableFunc IfTableFunc = 0;

		m_bMacAddressFound = false;

		hIpHelpAPI = LoadLibrary(TEXT("IPHLPAPI"));
		if (hIpHelpAPI)
		{
			IfTableFunc = (GetIfTableFunc)GetProcAddress(hIpHelpAPI, "GetIfTable");
		}

		if (IfTableFunc)
		{
			DWORD siz_if_table = 0;
			PMIB_IFTABLE pMIB_IFTABLE = 0;

			if ((IfTableFunc)(NULL, &siz_if_table, TRUE) == ERROR_INSUFFICIENT_BUFFER)
			{
				pMIB_IFTABLE = (PMIB_IFTABLE)malloc(siz_if_table);

				DWORD dwError = (IfTableFunc)(pMIB_IFTABLE, &siz_if_table, TRUE);

				if (pMIB_IFTABLE && !dwError)
				{
					DWORD nTableIndex = 0;

					memset(m_bytMACAddress, 255, MAC_ADDRESS_BYTE_LENGTH);

					/* First look for any Ethernet adapters */

					while ((nTableIndex < pMIB_IFTABLE->dwNumEntries &&
							pMIB_IFTABLE->table[nTableIndex].dwType != MIB_IF_TYPE_ETHERNET) == true)  // loop through all the entries, looking for an ethernet card
					{
						nTableIndex++;
					}

					/* Next look for any modems (not loopback) if no ethernet cannot be found */

					if (nTableIndex >= pMIB_IFTABLE->dwNumEntries)
					{
						nTableIndex = 0;
						while	(
									(nTableIndex < pMIB_IFTABLE->dwNumEntries &&
										pMIB_IFTABLE->table[nTableIndex].dwType != MIB_IF_TYPE_LOOPBACK) == true
								)  // loop through all the entries, looking for an ethernet card
						{
							nTableIndex++;
						}
					}

					/* if an entry is found . . . */

					if (nTableIndex < pMIB_IFTABLE->dwNumEntries)
					{
						memcpy(m_bytMACAddress, pMIB_IFTABLE->table[nTableIndex].bPhysAddr, MAC_ADDRESS_BYTE_LENGTH);
						m_bMacAddressFound = true;
					}
				}

				if (pMIB_IFTABLE)
				{
					free(pMIB_IFTABLE);
					pMIB_IFTABLE = 0;
				}
			}
		}

		if (hIpHelpAPI)
		{
			FreeLibrary(hIpHelpAPI);
		}

		if (!m_bMacAddressFound)
		{
			typedef struct _ASTAT_
			{
				ADAPTER_STATUS adapt;
				NAME_BUFFER    NameBuff [30];
			}ASTAT, * PASTAT;

			ASTAT		Adapter;
			NCB			Ncb;
			UCHAR		uRetCode;
			LANA_ENUM   lenum;

			memset( &Ncb, 0, sizeof(Ncb) );
			Ncb.ncb_command		= NCBENUM;
			Ncb.ncb_buffer		= (UCHAR *)&lenum;
			Ncb.ncb_length		= sizeof(lenum);
			uRetCode			= Netbios( &Ncb );

			memset( &Ncb, 0, sizeof(Ncb) );
			Ncb.ncb_command		= NCBRESET;
			Ncb.ncb_lana_num	= lenum.lana[0];
			uRetCode			= Netbios( &Ncb );

			memset( &Ncb, 0, sizeof (Ncb) );
			Ncb.ncb_command		= NCBASTAT;
			Ncb.ncb_lana_num	= lenum.lana[0];
			strcpy( (char *)Ncb.ncb_callname,  "*               " );
			Ncb.ncb_buffer		= (unsigned char *) &Adapter;
			Ncb.ncb_length		= sizeof(Adapter);

			uRetCode = Netbios( &Ncb );

			if ( uRetCode == 0 )
			{
				memcpy(m_bytMACAddress, Adapter.adapt.adapter_address, MAC_ADDRESS_BYTE_LENGTH);
				m_bMacAddressFound = true;
			}
		}

		if (!m_bMacAddressFound)
		{
			_ResetMACAddress();
		}
	}
	catch (...)
	{
		_ResetMACAddress();
		m_bMacAddressFound = false;
	}

}

// bwardlow 4-May-2006 #11814 use new sub key
#define GUID_REGISTRY_LOCATION	TEXT("SOFTWARE\\RealTime Gaming Software")
#define GUID_KEY				TEXT("RTGCLSID")
#define GUID_STRING_BUFFER_SIZE 1024


void
CUniqueMachineID::_GetGUIDFromClient()
{
	try
	{
		// Check to see if the GUID exists; if it doesn't, create one
		GUID oGUID;
		HKEY hKey;
		LONG lRet;

		m_bCustomGUIDFound = false;

		if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_LOCAL_MACHINE,
							GUID_REGISTRY_LOCATION,
							0,
							KEY_QUERY_VALUE,
							&hKey))
		{
			// read the key
			DWORD dwSize = GUID_STRING_BUFFER_SIZE;
			TCHAR szGUID[GUID_STRING_BUFFER_SIZE] = { '\0' };

			if (ERROR_SUCCESS == RegQueryValueEx(hKey,
				GUID_KEY,
				NULL,
				NULL,
				(LPBYTE)&szGUID,
				&dwSize))
			{
				if (RPC_S_OK == UuidFromString(szGUID, &oGUID))
				{
					m_bCustomGUIDFound = true;
				}
			}

			RegCloseKey(hKey);
		}

		if (!m_bCustomGUIDFound)
		{
			if (RPC_S_OK == UuidCreate(&oGUID))
			{
				if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_LOCAL_MACHINE,
							GUID_REGISTRY_LOCATION,
							0,
							KEY_WRITE,
							&hKey))
				{
					TCHAR * szGUID = 0;

					if (RPC_S_OK == UuidToString(&oGUID, &szGUID))
					{
						DWORD dwSize = _tcsclen(szGUID)+1 * sizeof(TCHAR);

						//save the key
						if (ERROR_SUCCESS == RegSetValueEx(	hKey,
															GUID_KEY,
															0,
															REG_SZ,
															(const unsigned char *) szGUID,
															dwSize))
						{
						}

						RpcStringFree(&szGUID);
					}

					RegCloseKey(hKey);
				}

				m_bCustomGUIDFound = true;
			}
		}

		if (m_bCustomGUIDFound)
		{
			memcpy(m_bytCustomGUID, &oGUID, GUID_BYTE_LENGTH);

		}
		else
		{
			_ResetCustomGUID();
		}
	}
	catch (...)
	{
		_ResetCustomGUID();
		m_bCustomGUIDFound = false;
	}
}

void
CUniqueMachineID::_GetSystemTimerGUID()
{
	/*_ResetSystemTimerGUID();

	SP_DEVINFO_DATA DeviceInfoData;
	DWORD dwIndex;

   // Create a HDEVINFO with all present devices.
	HDEVINFO hDevInfo = SetupDiGetClassDevs(	NULL,
												0, // Enumerator
												0,
												DIGCF_PRESENT | DIGCF_PROFILE | DIGCF_ALLCLASSES );

	if (hDevInfo == INVALID_HANDLE_VALUE)
	{
		return;
	}

	// Enumerate through all devices in Set.

	DeviceInfoData.cbSize = sizeof(SP_DEVINFO_DATA);

	for (dwIndex = 0; SetupDiEnumDeviceInfo(hDevInfo, dwIndex, &DeviceInfoData); dwIndex++)
	{
		DWORD DataT;
		char buffer[BUFFERSIZE];
		DWORD buffersize = BUFFERSIZE;

		//
		// Call function with null to begin with,
		// then use the returned buffer size
		// to Alloc the buffer. Keep calling until
		// success or an unknown failure.
		//

		memset (&buffer, '\0', BUFFERSIZE * sizeof(char));

		while (!SetupDiGetDeviceRegistryProperty(	hDevInfo,
													&DeviceInfoData,
													SPDRP_DEVICEDESC,
													&DataT,
													(PBYTE)buffer,
													BUFFERSIZE,
													&buffersize))
		{
			if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
			{
				break;
			}
		}

		if (buffer != 0)
		{
			bool bRetVal = strcmp(buffer, "System timer") == 0;

			if (bRetVal == true)
			{
				m_bSystemTimerGUIDFound = true;

				memcpy(m_bytSystemTimerGUID, &DeviceInfoData.ClassGuid, GUID_BYTE_LENGTH);


				break;
			}
		}

		memset (&buffer, '\0', BUFFERSIZE * sizeof(char));
	}

	//  Cleanup

	SetupDiDestroyDeviceInfoList(hDevInfo);*/


}

#define CURRENTVERSION_REGISTRY_LOCATION_WINDOWS	TEXT("SOFTWARE\\Microsoft\\Windows\\CurrentVersion")
#define CURRENTVERSION_REGISTRY_LOCATION_WINDOWSNT	TEXT("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion")
#define PRODUCTID_KEY								"ProductId"

//Windows NT

//HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion

void
CUniqueMachineID::_GetProductID()
{
	try
	{
		HKEY hKey;
		HRESULT hr			= ERROR_SUCCESS;
		m_bProductIDFound	= false;

		hr = RegOpenKeyEx(	HKEY_LOCAL_MACHINE,
							CURRENTVERSION_REGISTRY_LOCATION_WINDOWS,
							0,
							KEY_QUERY_VALUE,
							&hKey);

		if (ERROR_SUCCESS == hr)
		{

			// read the key
			DWORD dwSize = PRODUCT_ID_LENGTH;
			unsigned char szGUID[PRODUCT_ID_LENGTH] = { '\0' };

			if (ERROR_SUCCESS == ::RegQueryValueExA(	hKey,
													PRODUCTID_KEY,
													NULL,
													NULL,
													(LPBYTE)&szGUID,
													&dwSize))
			{

				memcpy(m_bytProductID, szGUID, dwSize);
				m_bProductIDFound = true;
			}

			RegCloseKey(hKey);
		}


		if (m_bProductIDFound == false)
		{
			hr = RegOpenKeyEx(	HKEY_LOCAL_MACHINE,
								CURRENTVERSION_REGISTRY_LOCATION_WINDOWSNT,
								0,
								KEY_QUERY_VALUE,
								&hKey);

			if (ERROR_SUCCESS == hr)
			{

				// read the key
				DWORD dwSize = PRODUCT_ID_LENGTH;
				unsigned char szGUID[PRODUCT_ID_LENGTH] = { '\0' };

				if (ERROR_SUCCESS == ::RegQueryValueExA(	hKey,
														PRODUCTID_KEY,
														NULL,
														NULL,
														(LPBYTE)&szGUID,
														&dwSize))
				{

					memcpy(m_bytProductID, szGUID, dwSize);
					m_bProductIDFound = true;
				}

				RegCloseKey(hKey);
			}
		}


	}
	catch (...)
	{
		_ResetProductID();
		m_bProductIDFound = false;
	}
}


#define NOIDENTITYFOUND 0

BYTE *
CUniqueMachineID::Serialize()
{
	// assumes pData is unallocated

	// pack the bytes of the three member variables into an array,
	//	accompanied by the bools indicating success in getting each
	//	value

	bool bUniqueIdFound = UniqueIdFound();

	m_nSerializedLength = bUniqueIdFound ?	(MAC_ADDRESS_BYTE_LENGTH + sizeof(bool8)) +
											(GUID_BYTE_LENGTH + sizeof(bool8)) +
											(HARD_DRIVE_SERIAL_NUMBER_LENGTH + sizeof(bool8)) +
											(PRODUCT_ID_LENGTH + sizeof(bool8)) : 1;

	if (m_pData) delete [] m_pData;

	m_pData = new unsigned char [m_nSerializedLength];

	if (m_pData)
	{
		memset(m_pData, '\0', m_nSerializedLength);

		if (bUniqueIdFound == true)
		{
			BYTE *pIndex = m_pData;

			// Hard Drive Serial Number

			memcpy(pIndex, m_bytHardDriveSerialNumber, HARD_DRIVE_SERIAL_NUMBER_LENGTH);
			pIndex += HARD_DRIVE_SERIAL_NUMBER_LENGTH;

			memcpy(pIndex, &m_bHardDriveSerialNumberFound, sizeof(bool8));
			pIndex += 1;

			// Mac Address

			memcpy(pIndex, m_bytMACAddress, MAC_ADDRESS_BYTE_LENGTH);
			pIndex += MAC_ADDRESS_BYTE_LENGTH;

			memcpy(pIndex, &m_bMacAddressFound, sizeof(bool8));
			pIndex += 1;

			// Custom GUID

			memcpy(pIndex, m_bytCustomGUID, GUID_BYTE_LENGTH);
			pIndex += GUID_BYTE_LENGTH;

			memcpy(pIndex, &m_bCustomGUIDFound, sizeof(bool8));
			pIndex += 1;

			// Product ID

			memcpy(pIndex, m_bytProductID, PRODUCT_ID_LENGTH);
			pIndex += PRODUCT_ID_LENGTH;

			memcpy(pIndex, &m_bProductIDFound, sizeof(bool8));

		}
		else
		{
			unsigned char bytNoGo;
			bytNoGo = NOIDENTITYFOUND;
			memcpy(m_pData, (const void *) &bytNoGo, 1);

		}
	}
	else
	{
		m_pData = 0;
	}

	return m_pData;

}


// kharmon.  05-14-07.  #19373.  Added data length parameter.
int32 CUniqueMachineID::Deserialize(BYTE *pData, int32 nDataLength)  
{
	if (!pData)
		return 0;

	uint8 length = *pData;

    if(length>nDataLength)
    {
        return 0;
    }

    return Deserialize(pData);
}


int32
CUniqueMachineID::Deserialize(BYTE *pData)
{
	_Reset();

	if (!pData)
		return 0;

	uint8 length = *pData + 1;

	uint8 offset = 1;

	if (length - offset < HARD_DRIVE_SERIAL_NUMBER_LENGTH)
		return length;

	memcpy(m_bytHardDriveSerialNumber, pData + offset, HARD_DRIVE_SERIAL_NUMBER_LENGTH);
	offset += HARD_DRIVE_SERIAL_NUMBER_LENGTH;

	if (length - offset < sizeof(bool8))
		return length;

	memcpy(&m_bHardDriveSerialNumberFound, pData + offset, sizeof(bool8));
	offset += sizeof(bool8);

	if (length - offset < MAC_ADDRESS_BYTE_LENGTH)
		return length;

	memcpy(m_bytMACAddress, pData + offset, MAC_ADDRESS_BYTE_LENGTH);
	offset += MAC_ADDRESS_BYTE_LENGTH;

	if (length - offset < sizeof(bool8))
		return length;

	memcpy(&m_bMacAddressFound, pData + offset, sizeof(bool8));
	offset += sizeof(bool8);

	if (length - offset < GUID_BYTE_LENGTH)
		return length;

	memcpy((void *)&m_bytCustomGUID, pData + offset, GUID_BYTE_LENGTH);
	offset += GUID_BYTE_LENGTH;

	if (length - offset < sizeof(bool8))
		return length;

	memcpy(&m_bCustomGUIDFound, pData + offset, sizeof(bool8));
	offset += sizeof(bool8);

	if (length - offset < PRODUCT_ID_LENGTH)
		return length;

	memcpy((void *)&m_bytProductID, pData + offset, PRODUCT_ID_LENGTH);
	offset += PRODUCT_ID_LENGTH;

	if (length - offset < sizeof(bool8))
		return length;

	memcpy(&m_bProductIDFound, pData + offset, sizeof(bool8));
	offset += sizeof(bool8);

	return length;
}

bool
CUniqueMachineID::operator == (const CUniqueMachineID &obj)
{
	// check the individual members to see if equal
	if (memcmp(this->m_bytHardDriveSerialNumber, obj.m_bytHardDriveSerialNumber, HARD_DRIVE_SERIAL_NUMBER_LENGTH)	== 0 &&
		memcmp(this->m_bytMACAddress, obj.m_bytMACAddress, MAC_ADDRESS_BYTE_LENGTH)									== 0 &&
		memcmp((void *)&this->m_bytCustomGUID, (const void *)&obj.m_bytCustomGUID, GUID_BYTE_LENGTH)				== 0 &&
		memcmp((void *)&this->m_bytSystemTimerGUID, (const void *)&obj.m_bytSystemTimerGUID, GUID_BYTE_LENGTH)		== 0)
	{
		return true;
	}

	return false;
}

